/* 
 * 
 * Copyright 2011 The University of Adelaide
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License. 
 *  
 */

package au.org.aekos.ands.global;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;

import au.org.aekos.ands.global.parse.DescriptionParser;
import au.org.aekos.ands.global.parse.IdentifierParser;
import au.org.aekos.ands.global.parse.LocationParser;
import au.org.aekos.ands.global.parse.NameParser;
import au.org.aekos.ands.global.parse.PropertyParser;
import au.org.aekos.ands.global.parse.RelatedInfoParser;
import au.org.aekos.ands.global.parse.SubjectParser;
import au.org.aekos.ands.global.populate.Populate;
import au.org.aekos.ands.global.populate.PopulateActivity;
import au.org.aekos.ands.global.populate.PopulateCollection;
import au.org.aekos.ands.global.populate.PopulateParty;
import au.org.ands.standards.rifCs.registryObjects.ActivityDocument.Activity;
import au.org.ands.standards.rifCs.registryObjects.CollectionDocument.Collection;
import au.org.ands.standards.rifCs.registryObjects.DescriptionType;
import au.org.ands.standards.rifCs.registryObjects.ElectronicAddressType;
import au.org.ands.standards.rifCs.registryObjects.IdentifierType;
import au.org.ands.standards.rifCs.registryObjects.LocationType;
import au.org.ands.standards.rifCs.registryObjects.LocationType.Address;
import au.org.ands.standards.rifCs.registryObjects.NameType;
import au.org.ands.standards.rifCs.registryObjects.PartyDocument.Party;
import au.org.ands.standards.rifCs.registryObjects.RegistryObjectsDocument.RegistryObjects.RegistryObject;
import au.org.ands.standards.rifCs.registryObjects.RegistryObjectsDocument.RegistryObjects.RegistryObject.OriginatingSource;
import au.org.ands.standards.rifCs.registryObjects.RelatedInfoType;
import au.org.ands.standards.rifCs.registryObjects.RelatedObjectType;
import au.org.ands.standards.rifCs.registryObjects.SpatialType;
import au.org.ands.standards.rifCs.registryObjects.SubjectType;
import au.org.ands.standards.rifCs.registryObjects.TemporalType;

import com.google.common.base.Splitter;

/**
 * Provides a mechanism for constructing
 * <a href="http://ands.org.au/resource/rif-cs.html">RIF-CS</a> objects and
 * interfacing with locally-defined metadata properties.
 */
public class Schema {
	private Properties properties = new Properties();
	
	/**
	 * An enum type for various global properties within the
	 * metadata configuration file.
	 */
	public static enum Prop {
		GROUP, SOURCE, SYSTEM, PARTY, IGNORE_ACTIVITY, IGNORE_COLLECTION, SUPERCOLLECTION;
		
		/* (non-Javadoc)
		 * @see java.lang.Enum#toString()
		 */
		@Override
		public String toString() { return name().toLowerCase().replace("_", "."); }
	}

	/**
	 * The three RIF-CS object types handled by this framework.
	 */
	public static enum EntityType {
		COLLECTION, ACTIVITY, PARTY;
		
		/* (non-Javadoc)
		 * @see java.lang.Enum#toString()
		 */
		@Override
		public String toString() { return name().toLowerCase(); }
	}
	
	/**
	 * The field types supported within the metadata configuration file.
	 */
	public static enum Field {
		KEY, IDENTIFIER, NAME, DESCRIPTION, SUBJECT, RELATED, LOCATION;
		
		/* (non-Javadoc)
		 * @see java.lang.Enum#toString()
		 */
		@Override
		public String toString() { return name().toLowerCase(); }
	}
	
	/**
	 * The field attributes supported within the metadata configuration file.
	 */
	public static enum Attribute {
		PRIMARY, ABBREVIATED, ALTERNATIVE, FULL, BRIEF, NOTES, LOCAL, ACCESSRIGHTS, ANZSRC_FOR, ANZSRC_TOA, ANZSRC_SEO;
		
		/* (non-Javadoc)
		 * @see java.lang.Enum#toString()
		 */
		@Override
		public String toString() { return name().replace("_", "-").toLowerCase(); }
	}
	
	/**
	 * The currently supported subset of RIF-CS <a href="http://ands.org.au/guides/cpguide/cpgrelatedobject.html">relations</a>.
	 */
	public static enum Relation {
		IS_PART_OF, IS_OUTPUT_OF, IS_MANAGED_BY, HAS_OUTPUT;

		/* (non-Javadoc)
		 * @see java.lang.Enum#toString()
		 */
		@Override
		public String toString() {
			String[] tokens = name().split("_");
			String s = tokens[0].toLowerCase();
			for (int i = 1; i < tokens.length; i++) {				
				s += StringUtils.capitalize(tokens[i].toLowerCase());
			}
			return s;
		}
	}

	private static final Map<EntityType,Prop> IGNORE_TYPE_MAP;
	
	/**
	 * A read-only {@link Set} which contains all RIF-CS fields which can be
	 * specified in the metadata configuration file.
	 */
	public static final Set<Field> ALL_FIELDS;

	private static final String DATE_FORMAT = "yyyy-MM-dd";
	private static final String DATE_FORMAT_SPEC = "W3CDTF";
					
	private Map<EntityType,Set<String>> ignore;
	
	private Populate<Collection> populateCollection;
	private Populate<Activity> populateActivity;
	private Populate<Party> populateParty;
	
	static {
		IGNORE_TYPE_MAP = new HashMap<EntityType,Prop>();
		IGNORE_TYPE_MAP.put(EntityType.ACTIVITY, Prop.IGNORE_ACTIVITY);
		IGNORE_TYPE_MAP.put(EntityType.COLLECTION, Prop.IGNORE_COLLECTION);
		
		ALL_FIELDS = Collections.unmodifiableSet(new HashSet<Field>(Arrays.asList(Field.values())));
	}
	
	/**
	 * Initialises the objects for parsing the metadata configuration and populating the relevant objects.
	 */
	private void init() {
		Map<Field,PropertyParser<?>> parsers = new HashMap<Field,PropertyParser<?>>();
		parsers.put(Field.IDENTIFIER, new IdentifierParser(properties));
		parsers.put(Field.NAME, new NameParser(properties));
		parsers.put(Field.DESCRIPTION, new DescriptionParser(properties));
		parsers.put(Field.SUBJECT, new SubjectParser(properties));
		parsers.put(Field.LOCATION, new LocationParser(properties));
		parsers.put(Field.RELATED, new RelatedInfoParser(properties));
		
		populateCollection = new PopulateCollection(parsers);
		populateActivity = new PopulateActivity(parsers);
		populateParty = new PopulateParty(parsers);
	}
	
	/**
	 * Initialises the sets of entity names which are to be ignored and <em>not</em> to be
	 * provided within the RIF-CS feed.
	 */
	private void initIgnores() {
		ignore = new HashMap<EntityType,Set<String>>();
		for (EntityType type : EntityType.values()) {
			Set<String> set = new HashSet<String>();
			ignore.put(type, set);
			ignore(type, set);
		}
	}
	
	/**
	 * Checks if the entity of given type and name is to be ignored.
	 *
	 * @param type the type.
	 * @param id the id.
	 * @return true, if is ignored, else false.
	 */
	public boolean isIgnored(EntityType type, String id) { return ignore.get(type).contains(id); }
	
	/**
	 * Retrieves the list of extra entities of the given type. Extra entities are those which are
	 * not fetched from a database or some other data source, but are simply specified, in full, within
	 * the metadata configuration file.
	 *
	 * @param type the entity type.
	 * @return a list of the identifiers of the extra entities of the given type, which may be empty.
	 */
	public List<String> getExtra(EntityType type) {
		String value = prop(type.toString() + ".extra");
		if (value == null) {
			return Collections.emptyList();
		} else {
			List<String> list = new ArrayList<String>();
			for (String s : Splitter.on(" ").omitEmptyStrings().trimResults().split(value)) {
				list.add(s);
			}
			return list;
		}
	}
	
	/**
	 * Instantiates a new schema object based on the metadata configuration properties
	 * provided through the given stream.
	 *
	 * @param propStream the property stream.
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public Schema(InputStream propStream) throws IOException {
		properties.load(propStream);
		
		init();
		initIgnores();
	}
	
	/**
	 * Shortcut for retrieving a property with the given key.
	 *
	 * @param key the key.
	 * @return the string, if one is mapped from the given key, else null.
	 */
	public String prop(String key) { return properties.getProperty(key); }
	
	/**
	 * Retrieves the property with the given key.
	 *
	 * @param key the key.
	 * @return the string, if one is mapped from the given key, else null.
	 */
	public String prop(Prop key) { return prop(key.toString()); }
	
	/**
	 * Retrieves the RIF-CS
	 * <a href="http://ands.org.au/guides/cpguide/cpggroup.html">group</a>
	 * specified by the metadata properties.
	 *
	 * @return the group
	 */
	public String getGroup() { return prop(Prop.GROUP); }
	
	/**
	 * Generates a key for the given entity type with the assumption of a null identifier.
	 *
	 * @param type the type
	 * @return the string
	 */
	public String generateKey(EntityType type) {
		return generateKey(type, null);
	}
	
	/**
	 * Generates a key for the object
	 * specified by the given identifier and entity type. The key string is constructed
	 * by concatenating the four following properties, separated by forward slash characters:
	 * <p>
	 * SYSTEM/TYPE/SOURCE/IDENTIFIER
	 * <p>
	 * For example:
	 * <p>
	 * aekos.org.au/collection/somedatasource/42
	 *
	 * @param type the entity type.
	 * @param identifier the identifier of the object.
	 * @return the key.
	 */
	public String generateKey(EntityType type, String identifier) {
		String key = prop(Prop.SYSTEM) + "/" + type + "/" + prop(Prop.SOURCE);
		if (identifier != null) { key += "/" + identifier; }
		return key.toLowerCase();
	}
	
	/**
	 * Sets the key for the given
	 * <a href="http://ands.org.au/guides/cpguide/cpgrecstructure.html">Registry Object</a>.
	 * The entity type and identifier are first used to lookup a key in the metadata
	 * configuration. If this search finds a key, it will be used, otherwise the value of
	 * the defaultKey parameter will be used.
	 * 
	 *
	 * @param ro the registry object.
	 * @param type the entity's type.
	 * @param identifier the entity's identifier.
	 * @param defaultKey the default key.
	 */
	public void setKey(RegistryObject ro, EntityType type, String identifier, String defaultKey) {
		String key = prop(type + "." + identifier + ".key");
		if (key != null) {
			ro.setKey(key);
		} else {
			ro.setKey(defaultKey);
		}
	}
	
	/**
	 * Generates a relation to the party specified in the metadata configuration.
	 *
	 * @return the relation.
	 */
	public RelatedObjectType linkToParty() {
		return linkToParty(prop(Prop.PARTY));
	}
	
	/**
	 * Generates a relation to the party specified by the identifier parameter.
	 *
	 * @param identifier the target party's identifier.
	 * @return the relation.
	 */
	public RelatedObjectType linkToParty(String identifier) {
		return generateRelation(Relation.IS_MANAGED_BY, identifier);
	}
	
	/**
	 * Generates a relation to an activity without including a trailing activity identifier.
	 *
	 * @return the relation.
	 */
	public RelatedObjectType linkToActivity() { return linkToActivity(null); }
	
	/**
	 * Generates a relation to an activity specified with the given identifier.
	 *
	 * @param identifier the identifier
	 * @return the related object type
	 */
	public RelatedObjectType linkToActivity(String identifier) {
		return generateRelation(Relation.IS_OUTPUT_OF, generateKey(EntityType.ACTIVITY, identifier));
	}
	
	// Loads the list of entity identifiers to ignore from the metadata configuration.
	private void ignore(EntityType type, Set<String> set) {
		Prop prop = IGNORE_TYPE_MAP.get(type);
		if (prop == null) { return; }
		String ignoreProperty = prop(prop);
		if (ignoreProperty != null) {
			for (String token : ignoreProperty.split(" +")) {
				set.add(token);
			}
		}
	}
	
	// Activity
	
	/**
	 * Generates an empty activity within the supplied container.
	 *
	 * @param container the container.
	 * @return the new, empty activity.
	 */
	public Activity generateEmptyActivity(RegistryObject container) {
		container.setOriginatingSource(generateSource());
		container.setGroup(getGroup());

		Activity activity = container.addNewActivity();
		activity.setType("project");
		
		return activity;
	}
		
	/**
	 * Generates an activity with the given identifier.  Attempts to load all supported fields
	 * from the metadata configuration.
	 *
	 * @param id the identifier of the activity.
	 * @return a new registry object which will contain the new activity.
	 */
	public RegistryObject generateActivity(String id) { return generateActivity(id, ALL_FIELDS); }
	
	/**
	 * Generates an activity with the given identifier, restricted to the supplied set of RIF-CS fields.
	 *
	 * @param id the identifier of the activity.
	 * @param fields the fields which the method will attempt to load from the configuration.
	 * @return a new registry object which will contain the new activity.
	 */
	public RegistryObject generateActivity(String id, Set<Field> fields) {
		RegistryObject container = RegistryObject.Factory.newInstance();
    	
		Activity activity = generateEmptyActivity(container);
		
		// Party
		
		activity.addNewRelatedObject().set(linkToParty());

		// Key

		String defaultKey = generateKey(EntityType.ACTIVITY, id);

		setKey(container, EntityType.ACTIVITY, id, defaultKey);
		
		// Identifier, Names, Descriptions, Subjects, Locations, Related Info

		populateActivity.populate(activity, fields, id);

		if (activity.getIdentifierArray().length == 0) {
			IdentifierType identifier = activity.addNewIdentifier();
			identifier.setType(Attribute.LOCAL.toString());
			identifier.setStringValue(defaultKey);
		}

		return container;
	}
	
	// Party
	
	/**
	 * Generates an empty party within the supplied container.
	 *
	 * @param container the container.
	 * @return the new, empty party.
	 */
	public Party generateEmptyParty(RegistryObject container) {
		container.setOriginatingSource(generateSource());
		container.setGroup(getGroup());

		Party party = container.addNewParty();
		party.setType("project");
		
		return party;
	}

	/**
	 * Generates a party with the given identifier.  Attempts to load all supported fields
	 * from the metadata configuration.
	 *
	 * @param id the identifier of the activity.
	 * @return a new registry object which will contain the new activity.
	 */
	public RegistryObject generateParty(String id) { return generateParty(id, ALL_FIELDS); }

	/**
	 * Generates a party with the given identifier, restricted to the supplied set of RIF-CS fields.
	 *
	 * @param id the identifier of the party.
	 * @param fields the fields which the method will attempt to load from the configuration.
	 * @return a new registry object which will contain the new activity.
	 */
	public RegistryObject generateParty(String id, Set<Field> fields) {
		RegistryObject container = RegistryObject.Factory.newInstance();

		Party party = generateEmptyParty(container);

		// Key
				
		String defaultKey = generateKey(EntityType.PARTY, id);
		
		setKey(container, EntityType.PARTY, id, defaultKey);
		
		// Identifier, Names, Descriptions, Subjects, Locations, Related Info
		
		populateParty.populate(party, fields, id);

		if (party.getIdentifierArray().length == 0) {
			party.addNewIdentifier().set(generateIdentifier(Attribute.LOCAL, id));
		}		
		
		return container;
	}
	
	// Collection
	
	/**
	 * Generates an empty collection within the supplied container.
	 *
	 * @param container the container.
	 * @return the new, empty collection.
	 */
	public Collection generateEmptyCollection(RegistryObject container) {
		container.setOriginatingSource(generateSource());
		container.setGroup(getGroup());

		Collection collection = container.addNewCollection();
		collection.setType("dataset");

		return collection;
	}
	
	/**
	 * Generates a collection with the given identifier.  Attempts to load all supported fields
	 * from the metadata configuration.
	 *
	 * @param id the identifier of the collection.
	 * @return a new registry object which will contain the new collection.
	 */
	public RegistryObject generateCollection(String id) { return generateCollection(id, ALL_FIELDS); }
	
	/**
	 * Generates a collection with the given identifier, restricted to the supplied set of RIF-CS fields.
	 *
	 * @param id the identifier of the collection.
	 * @param fields the fields which the method will attempt to load from the configuration.
	 * @return a new registry object which will contain the new collection.
	 */
	public RegistryObject generateCollection(String id, Set<Field> fields) {
		RegistryObject container = RegistryObject.Factory.newInstance();
    	
		Collection collection = generateEmptyCollection(container);
		
		// Party
		
		collection.addNewRelatedObject().set(linkToParty());

		// Key

		String defaultKey = generateKey(EntityType.COLLECTION, id);

		setKey(container, EntityType.COLLECTION, id, defaultKey);

		// Names, Descriptions, Subjects, Locations, Related Info

		populateCollection.populate(collection, fields, id);
		
		if (collection.getIdentifierArray().length == 0) {
			IdentifierType identifier = collection.addNewIdentifier();
			identifier.setType(Attribute.LOCAL.toString());
			identifier.setStringValue(defaultKey);
		}
		
		// Super Collection
		
		String superKey = prop(Prop.SUPERCOLLECTION);
		if (superKey != null) {
			// don't point supercollection at itself
			if (!container.getKey().equalsIgnoreCase(superKey)) {
				collection.addNewRelatedObject().set(generateRelation(Relation.IS_PART_OF, superKey));
			}
		}

		return container;
	}
		
	/**
	 * Generates a relation of the given type to the specified target name.
	 *
	 * @param type the relation type.
	 * @param value the target name.
	 * @return the relation.
	 */
	public static RelatedObjectType generateRelation(Relation type, String value) {
		RelatedObjectType relation = RelatedObjectType.Factory.newInstance();
		relation.setKey(value);
		relation.addNewRelation().setType(type.toString());
		return relation;
	}
	
	/**
	 * Generates a relation to an external resource specified by a URL.
	 *
	 * @param title the title of the resource.
	 * @param url the URL of the resource.
	 * @return the relation.
	 */
	public static RelatedInfoType generateRelatedURL(String title, URL url) {
		RelatedInfoType related = RelatedInfoType.Factory.newInstance();
		IdentifierType id = related.addNewIdentifier();
		id.setType("uri");
		id.setStringValue(url.toString());
		related.addNewTitle().setStringValue(title);
		return related;
	}
	
	/**
	 * Generates the default
	 * <a href="http://www.ands.org.au/guides/cpguide/cpgorigsource.html">originating source</a>.
	 *
	 * @return the originating source.
	 */
	public OriginatingSource generateSource() {
		return generateSource(prop(Prop.SOURCE));
	}
	
	/**
	 * Generates an
	 * <a href="http://www.ands.org.au/guides/cpguide/cpgorigsource.html">originating source</a>,
	 * using the specified identifier.
	 *
	 * @param value the identifier of the originating source.
	 * @return the originating source.
	 */
	public static OriginatingSource generateSource(String value) {
		OriginatingSource source = OriginatingSource.Factory.newInstance();
		source.setType("authoritative");
		source.setStringValue(value);
		return source;
	}

	/**
	 * Generates a
	 * <a href="http://www.ands.org.au/guides/cpguide/cpgdescription.html">description</a>
	 * of the given type and value.
	 *
	 * @param type the description type.
	 * @param value the description content.
	 * @return the description.
	 */
	public static DescriptionType generateDescription(Attribute type, String value) {
		return generateDescription(type.toString(), value);
	}
	
	/**
	 * Generates a
	 * <a href="http://www.ands.org.au/guides/cpguide/cpgdescription.html">description</a>
	 * of the given type and value.
	 *
	 * @param type the description type.
	 * @param value the description content.
	 * @return the description.
	 */
	public static DescriptionType generateDescription(String type, String value) {
		DescriptionType description = DescriptionType.Factory.newInstance();
		description.setType(type);
		description.setStringValue(value);
		return description;
	}
	
	/**
	 * Generates a
	 * <a href="http://www.ands.org.au/guides/cpguide/cpgname.html">name</a>
	 * of the given type and value.
	 *
	 * @param type the name type.
	 * @param value the name content.
	 * @return the name.
	 */
	public static NameType generateName(Attribute type, String value) {
		return generateName(type.toString(), value);
	}
	
	/**
	 * Generates a
	 * <a href="http://www.ands.org.au/guides/cpguide/cpgname.html">name</a>
	 * of the given type and value.
	 *
	 * @param type the name type.
	 * @param value the name content.
	 * @return the name.
	 */
	public static NameType generateName(String type, String value) {
		NameType name = NameType.Factory.newInstance();
		name.setType(type);
		name.addNewNamePart().setStringValue(value);
		return name;
	}
	
	/**
	 * Generates an
	 * <a href="http://www.ands.org.au/guides/cpguide/cpgidentifiers.html">identifier</a>
	 * of the given type and value.
	 *
	 * @param type the identifier type.
	 * @param value the identifier content.
	 * @return the identifier.
	 */
	public static IdentifierType generateIdentifier(Attribute type, String value) {
		return generateIdentifier(type.toString(), value);
	}
	
	/**
	 * Generates an
	 * <a href="http://www.ands.org.au/guides/cpguide/cpgidentifiers.html">identifier</a>
	 * of the given type and value.
	 *
	 * @param type the identifier type.
	 * @param value the identifier content.
	 * @return the identifier.
	 */
	public static IdentifierType generateIdentifier(String type, String value) {
		IdentifierType identifier = IdentifierType.Factory.newInstance();
		identifier.setType(type);
		identifier.setStringValue(value);
		return identifier;
	}	
	
	/**
	 * Generates
	 * <a href="http://www.ands.org.au/guides/cpguide/cpgcoverage.html">spatial coverage</a>
	 * using the given bounding box.
	 *
	 * @param north the north edge.
	 * @param east the east edge.
	 * @param south the south edge.
	 * @param west the west edge.
	 * @param datum the datum.
	 * @return the spatial coverage.
	 */
	public static SpatialType generateSpatialCoverage(double north, double east, double south, double west, String datum) {
		SpatialType spatial = SpatialType.Factory.newInstance();
		spatial.setType("iso19139dcmiBox");
    	String box = "northlimit=NORTH; southlimit=SOUTH; westlimit=WEST; eastlimit=EAST; projection=PROJ";
    	box = box.replaceAll("NORTH", Double.toString(north)).replaceAll("SOUTH", Double.toString(south)).replaceAll("EAST", Double.toString(east)).replaceAll("WEST", Double.toString(west)).replaceAll("PROJ", datum);
    	spatial.setStringValue(box);
    	return spatial;
	}
	
	/**
	 * Generates
	 * <a href="http://www.ands.org.au/guides/cpguide/cpgcoverage.html">temporal coverage</a>
	 * spanning the range between the two given dates.
	 *
	 * @param minDate the start date.
	 * @param maxDate the end date.
	 * @return the temporal coverage.
	 */
	public static TemporalType generateTemporalCoverage(Date minDate, Date maxDate) {
    	SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);

		TemporalType temporal = TemporalType.Factory.newInstance();

		TemporalType.Date dateFrom = temporal.addNewDate();
		dateFrom.setType("dateFrom");
		dateFrom.setDateFormat(DATE_FORMAT_SPEC);
		dateFrom.setStringValue(dateFormat.format(minDate));
		
		TemporalType.Date dateTo = temporal.addNewDate();
		dateTo.setType("dateTo");
		dateTo.setDateFormat(DATE_FORMAT_SPEC);
		dateTo.setStringValue(dateFormat.format(maxDate));
		
		return temporal;
	}
	
	/**
	 * Generates a list of subjects of the given type and values.
	 *
	 * @param type the subject type.
	 * @param values the subject values.
	 * @return the list of subjects.
	 */
	public static List<SubjectType> generateSubjects(Attribute type, java.util.Collection<String> values) {
		return generateSubjects(type.toString(), values);
	}
	
	/**
	 * Generates a list of subjects of the given type and values.
	 *
	 * @param type the subject type.
	 * @param values the subject values.
	 * @return the list of subjects.
	 */
	public static List<SubjectType> generateSubjects(String type, java.util.Collection<String> values) {
		List<SubjectType> subjects = new ArrayList<SubjectType>();
		for (String value : values) {
			SubjectType subject = SubjectType.Factory.newInstance();
			subject.setType(type);
			subject.setStringValue(value);
			subjects.add(subject);
		}
		return subjects;
	}
}
